﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Hook.Primitives;
using Microsoft.Xna.Framework;
using Hook.Graphics;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Storage;
using System.IO;

namespace Hook.Environmental.Props
{
    public class Prop
    {
        public const int BACKGROUND = 0;
        public const int MIDGROUND = 1;
        public const int FOREGROUND = 2;

        public string Name;
        public Sprite Sprite;
        public Polygon Frame;
        public List<Prop> AttachedProps;
        public List<HingeLink> HingeLinks;
        public Prop MasterProp;
        public Vector3 Position, Velocity, Acceleration;
        public Vector3 Rotation, AngularVelocity, AngularAcceleration;
        public Vector3 QueuedForce, QueuedTorque;
        public float Mass, Inertia;
        public List<int> CollisionLayer;
        public bool GravityOn, Grounded, RootedTo, Removing;
        public double RemovalTimer, WaitTimer;
        public int DrawPriority;

        /// <summary>
        /// Generates the default values for a prop.
        /// </summary>
        public Prop()
        {
            Name = "Untitled.txt";
            Position = Velocity = Acceleration = Vector3.Zero;
            Rotation = AngularVelocity = AngularAcceleration = Vector3.Zero;
            Mass = 1;
            Inertia = 1000;
            CollisionLayer = new List<int>();
            Frame = new Polygon(new PolyPoint(Position), new List<Vector3>());
            AttachedProps = new List<Prop>();
            HingeLinks = new List<HingeLink>();
            MasterProp = null;
            GravityOn = false;
            DrawPriority = MIDGROUND;
        }

        public Prop(string PropName)
        {
            Name = PropName;
            Position = Velocity = Acceleration = Vector3.Zero;
            Rotation = AngularVelocity = AngularAcceleration = Vector3.Zero;
            CollisionLayer = new List<int>();
            Frame = new Polygon(new PolyPoint(Position), new List<Vector3>());
            AttachedProps = new List<Prop>();
            HingeLinks = new List<HingeLink>();
            MasterProp = null;

            LoadPropData(Name);
        }

        public Prop(string PropName, Vector3 Position, Vector3 Rotation)
        {
            Name = PropName;
            Velocity = Acceleration = Vector3.Zero;
            AngularVelocity = AngularAcceleration = Vector3.Zero;
            CollisionLayer = new List<int>();
            Frame = new Polygon(new PolyPoint(Position), new List<Vector3>());
            AttachedProps = new List<Prop>();
            HingeLinks = new List<HingeLink>();
            MasterProp = null;

            LoadPropData(Name);

            SetPosition(Position);
            SetRotation(Rotation);
        }

        public void Update(GameTime gt)
        {
            /*foreach (Prop P in AttachedProps)
            {
                P.Frame.ReferencePoint.Position = Position;
                P.Frame.RotationOffset = Rotation;

                //P.Position = P.Frame.ReferencePoint.Position + P.Frame.RotateOffset(P.Frame.OriginOffset, Rotation);
                P.Rotation = P.Frame.Rotation + P.Frame.RotationOffset;

                P.Update(gt);
            }*/
            if (RemovalTimer > 0)
            {
                RemovalTimer -= gt.ElapsedGameTime.TotalSeconds;
                if (RemovalTimer <= 0)
                {
                    RemovalTimer = 0;
                    Removing = true;
                }
            }
            if (WaitTimer > 0)
            {
                WaitTimer -= gt.ElapsedGameTime.TotalSeconds;
                if (WaitTimer <= 0)
                {
                    WaitTimer = 0;
                }
            }

            ApplyForceAndTorque();

            Frame.Update(gt);

            if(Sprite != null)
            {
                Sprite.Update(gt);
            }
            //Frame.ReferencePoint.Position = Position - Frame.OriginOffset;
            //Frame.OriginOffset = Position - Frame.ReferencePoint.Position;
        }
        public void Draw(SpriteBatch sb, Camera Cam)
        {
            if (PropOnScreen(Cam))
            {
                if (Sprite != null)
                    Sprite.Draw(sb, Cam);
                Frame.Draw(sb, Cam);
            }

            foreach(Prop P in AttachedProps)
            {
                P.Draw(sb, Cam);
            }
        }

        public bool PropOnScreen(Camera Cam)
        {
            float CenterDistance = (Position - Cam.Position).Length();
            float PropRadius;
            if (Frame.FarthestPointIndex == -1)
            {
                PropRadius = 0;
            }
            else
            {
                PropRadius = Frame.Offsets[Frame.FarthestPointIndex].Length();
            }
            if (CenterDistance < Math.Sqrt(Math.Pow(Game1.RESOLUTION.X / 2, 2) + Math.Pow(Game1.RESOLUTION.Y / 2, 2)) + PropRadius)
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        public void Move()
        {
            ConstrainVelocities();

            SetPosition(Position + Velocity);
            Velocity += Acceleration;

            SetRotation(Rotation + AngularVelocity);
            AngularVelocity += AngularAcceleration;

            Frame.ReferencePoint.Position = Position;
            Frame.Rotation = Rotation - Frame.RotationOffset;

            if (Sprite != null)
            {
                Sprite.Position = Position;
                Sprite.Rotation = Rotation;
            }
        }

        public void SetPosition(Vector3 P)
        {
            Position = P;
            Frame.ReferencePoint.Position = P;
            Frame.SetPoints();
            if(Sprite != null)
                Sprite.Position = P;
        }

        public void SetRotation(Vector3 R)
        {
            Rotation = R;
            Frame.Rotation = R;
            Frame.SetPoints();
            if (Sprite != null)
                Sprite.Rotation = R;
        }

        public void QueueForce(Vector3 Force)
        {
            float Length = Force.Length();
            if (!float.IsInfinity(Length) && !float.IsNaN(Length))
                QueuedForce += Force;
        }
        public void QueueTorque(Vector3 Torque)
        {
            float Length = Torque.Length();
            if (!float.IsInfinity(Length) && !float.IsNaN(Length))
                QueuedTorque += Torque;
        }
        public void QueueForceWithOffset(Vector3 Force, Vector3 Offset)
        {
            Vector3 CenterForce, TorqueForce;
            CenterForce = Prop.Projection(-Offset, Force);
            TorqueForce = Force - CenterForce;

            QueueForce(CenterForce);
            QueueTorque(Vector3.Cross(Offset, TorqueForce));
        }

        public void ApplyForceAndTorque()
        {
            Velocity += QueuedForce / Mass;
            AngularVelocity += QueuedTorque / Inertia;

            QueuedForce = QueuedTorque = Vector3.Zero;
        }

        /*public void MoveTowardHinge()
        {
            HingeVel = Vector2.Zero;

            foreach (HingeLink H in HingeLinks)
            {
                Vector2 Distance;
                float MassFactor;
                if (H.Prop1 == this)
                {
                    Distance = (H.Prop2.Frame.OriginOffset + Prop.RotateVector(H.HingeOffset2, H.Prop2.Rotation)) - (H.Prop1.Frame.OriginOffset + Prop.RotateVector(H.HingeOffset1, H.Prop1.Rotation));
                    MassFactor = H.Prop2.Mass / (Mass + H.Prop2.Mass);
                }
                else
                {
                    Distance = (H.Prop1.Frame.OriginOffset + Prop.RotateVector(H.HingeOffset1, H.Prop1.Rotation)) - (H.Prop2.Frame.OriginOffset + Prop.RotateVector(H.HingeOffset2, H.Prop2.Rotation));
                    MassFactor = H.Prop1.Mass / (Mass + H.Prop1.Mass);
                }
                if (Distance.Length() > 2)
                {
                    HingeVel += MassFactor * Distance / 2;
                }
            }
            Velocity += HingeVel;
        }*/

        /*public void RotateTowardHinge()
        {
            HingeRot = 0;

            foreach (HingeLink H in HingeLinks)
            {
                Vector2 P1Direction, P2Direction;
                float Angle, P1Angle, P2Angle;

                P1Direction = -RotateVector(H.HingeOffset1, H.Prop1.Rotation);
                P2Direction = -RotateVector(H.HingeOffset2, H.Prop2.Rotation);

                if (P2Direction.X != 0)
                    P2Angle = (float)Math.Atan(P2Direction.Y / P2Direction.X);
                else
                    P2Angle = -(float)Math.PI / 2;
                if (P2Direction.X < 0)
                    P2Angle += (float)Math.PI;

                while (P2Angle >= 2 * Math.PI)
                    P2Angle -= 2 * (float)Math.PI;
                while (P2Angle < 0)
                    P2Angle += 2 * (float)Math.PI;

                if (P1Direction.X != 0)
                    P1Angle = (float)Math.Atan(P1Direction.Y / P1Direction.X);
                else
                    P1Angle = -(float)Math.PI / 2;
                if (P1Direction.X < 0)
                    P1Angle += (float)Math.PI;

                while (P1Angle >= 2 * Math.PI)
                    P1Angle -= 2 * (float)Math.PI;
                while (P1Angle < 0)
                    P1Angle += 2 * (float)Math.PI;

                if (H.Prop1 == this)
                {
                    /*Direction = -(H.Prop2.Frame.OriginOffset + Prop.RotateVector(H.HingeOffset2, H.Prop2.Rotation));
                    VelocityInDirection = Projection(Direction, H.Prop2.Velocity);
                    Direction.Normalize();
                    VelocityInDirection.Normalize();

                    if (Direction == VelocityInDirection)
                    {
                        
                    Angle = P2Angle - P1Angle;

                    //}
                }
                else
                {
                    Angle = P1Angle - P2Angle;

                    /*Direction = -(H.Prop1.Frame.OriginOffset + Prop.RotateVector(H.HingeOffset1, H.Prop1.Rotation));
                    VelocityInDirection = Projection(Direction, H.Prop1.Velocity);
                    Direction.Normalize();
                    VelocityInDirection.Normalize();

                    if (Direction == VelocityInDirection)
                    {
                        Angle = H.Prop1.Rotation - H.Prop2.Rotation;

                        if (Angle > .05f)
                        {
                            HingeRot += -(Angle / 8);
                        }
                        else if (Angle < -.05f)
                        {
                            HingeRot += Angle / 8;
                        }
                    }
                }
                if (Angle > .35f)
                {
                    HingeRot += Angle / 256;
                }
                else if (Angle < -.35f)
                {
                    HingeRot -= Angle / 256;
                }
            }
            AngularVelocity += HingeRot;
        }*/

        /*public void BumpTowardHinged()
        {
            Vector2 TotalBump = Vector2.Zero;

            foreach (HingeLink H in HingeLinks)
            {
                Vector2 Distance;
                if (H.Prop1 == this)
                {
                    Distance = (H.Prop2.Frame.OriginOffset + Prop.RotateVector(H.HingeOffset2, H.Prop2.Rotation)) - (H.Prop1.Frame.OriginOffset + Prop.RotateVector(H.HingeOffset1, H.Prop1.Rotation));
                }
                else
                {
                    Distance = (H.Prop1.Frame.OriginOffset + Prop.RotateVector(H.HingeOffset1, H.Prop1.Rotation)) - (H.Prop2.Frame.OriginOffset + Prop.RotateVector(H.HingeOffset2, H.Prop2.Rotation));
                }
                TotalBump += Distance / 2;
            }

            Frame.OriginOffset += TotalBump;
            Position = Frame.ReferencePoint.Position + Frame.RotateOffset(Frame.OriginOffset, Frame.RotationOffset);
        }

        public void ForceHingedProps()
        {
            foreach (HingeLink H in HingeLinks)
            {
                if (H.Prop1 == this)
                {
                    Vector2 PointVel = Velocity + AngularVelocity * Prop.PerpendicularOfVector(H.HingeOffset1);

                    Vector2 CenterForce = Prop.Projection(Prop.RotateVector(H.HingeOffset2, H.Prop2.Rotation), PointVel);
                    Vector2 RotationVector = PointVel - CenterForce;
                    Vector2 Radius = Prop.RotateVector(H.HingeOffset2, H.Prop2.Rotation);
                    
                    float RotationForce;
                    if (Radius.X == 0 && Radius.Y == 0)
                        RotationForce = 0;
                    else if (Radius.X != 0)
                        RotationForce = RotationVector.Y / Radius.X;
                    else
                        RotationForce = RotationVector.X / -Radius.Y;

                    H.Prop2.Velocity += CenterForce / 16;
                    H.Prop2.AngularVelocity += RotationForce / 16;
                }
                else
                {
                    Vector2 PointVel = Velocity + AngularVelocity * Prop.PerpendicularOfVector(H.HingeOffset2);

                    Vector2 CenterForce = Prop.Projection(Prop.RotateVector(H.HingeOffset1, H.Prop1.Rotation), PointVel);
                    Vector2 RotationVector = PointVel - CenterForce;
                    Vector2 Radius = Prop.RotateVector(H.HingeOffset1, H.Prop1.Rotation);

                    float RotationForce;
                    if (Radius.X == 0 && Radius.Y == 0)
                        RotationForce = 0;
                    else if (Radius.X != 0)
                        RotationForce = RotationVector.Y / Radius.X;
                    else
                        RotationForce = RotationVector.X / -Radius.Y;

                    H.Prop1.Velocity += CenterForce / 16;
                    H.Prop1.AngularVelocity += RotationForce / 16;
                }
            }
        }*/

        public void ConstrainVelocities()
        {
            if (Velocity.Length() > GameEnvironment.MAXVEL)
            {
                Velocity.Normalize();
                Velocity *= GameEnvironment.MAXVEL;
            }

            if (AngularVelocity.Length() > GameEnvironment.MAXANGLEVEL){
                AngularVelocity.Normalize();
                AngularVelocity *= GameEnvironment.MAXANGLEVEL;
            }
        }

        /// <summary>
        /// Places a prop inside the current prop's subprop list.
        /// </summary>
        public void AttachProp(Prop P)
        {
            P.Frame.ReferencePoint.Position = Position;
            P.MasterProp = this;
            AttachedProps.Add(P);
        }

        public void AddHingePoint(Vector3 HingeOffset, Prop OtherProp, Vector3 OtherHingeOffset)
        {
            HingeLink HL = new HingeLink(this, HingeOffset, OtherProp, OtherHingeOffset);
            HingeLinks.Add(HL);
            OtherProp.HingeLinks.Add(HL);
        }

        /// <summary>
        /// Checks if the given point is within the frame polygon.
        /// </summary>
        public bool PointCollides(PolyPoint P)
        {
            bool PointInside = true;
            for(int l = 0; l < Frame.OuterLines.Count; l++)
            {
                if(!Frame.OuterLines[l].BoundsPoint(P))
                {
                    PointInside = false;
                }
            }
            return PointInside;
        }

        /// <summary>
        /// Returns the line closest to the given point.
        /// </summary>
        public Line LineClosestToPoint(PolyPoint P)
        {
            Line CloseLine = Frame.OuterLines[0];
            for(int l = 1; l < Frame.OuterLines.Count; l++)
            {
                Line TryLine = Frame.OuterLines[l];
                if(TryLine.PointDistance(P).Length() < CloseLine.PointDistance(P).Length())
                {
                    CloseLine = TryLine;
                }
            }
            return CloseLine;
        }

        /// <summary>
        /// Returns the distance of the closest line to a given point as a vector.
        /// </summary>
        public Vector3 ShortestPath(PolyPoint P)
        {
            Vector3 MinVector = new Vector3(3000, 4000, 5000);
            for(int l = 0; l < Frame.OuterLines.Count; l++)
            {
                Vector3 TryVector = Frame.OuterLines[l].PointDistance(P);
                if(TryVector.Length() < MinVector.Length())
                {
                    MinVector = TryVector;
                }
            }
            return MinVector;
        }

        /// <summary>
        /// Returns the distance of the farthest point intersecting the outer lines of the prop on the given line.
        /// </summary>
        public Vector3 HighLinePath(Line L)
        {
            Vector3 HighVector = Vector3.Zero;
            for (int l = 0; l < Frame.OuterLines.Count; l++)
            {
                Line TryLine = Frame.OuterLines[l];
                PolyPoint Intersection = TryLine.Intersection(L);
                if (TryLine.ContainsPoint(Intersection))
                {
                    Vector3 Distance = Intersection.Position - L.Start.Position;
                    if (Distance.Y < HighVector.Y)
                    {
                        HighVector = Distance;
                    }
                }
            }
            return HighVector;
        }

        /// <summary>
        /// Returns the prop's environment relative velocity.
        /// </summary>
        /*public Vector3 AbsoluteVelocity()
        {
            if (MasterProp != null)
            {
                Vector3 Distance = Position - MasterProp.Position;
                Vector3 DistancePerp = PerpendicularOfVector(Distance);
                Vector3 LinearVelocity = MasterProp.AngularVelocity * DistancePerp;

                return MasterProp.AbsoluteVelocity() + LinearVelocity;
            }
            else
            {
                return Velocity;
            }
        }*/

        /// <summary>
        /// Returns the prop's environment relative rotation.
        /// </summary>
        /*public float AbsoluteRotation()
        {
            if (MasterProp != null)
            {
                return Rotation + MasterProp.AbsoluteRotation();
            }
            else
            {
                return Rotation;
            }
        }*/

        /// <summary>
        /// Returns the rotation of the prop's masters.
        /// </summary>
        /*public float TotalUpperRotation()
        {
            return AbsoluteRotation() - Rotation;
        }*/

        /// <summary>
        /// Applies current velocity to master above the current prop.
        /// </summary>
        /*public void ApplyMomentumToMaster()
        {
            if (MasterProp != null)
            {
                float Torque = Velocity.Y * Mass;
                Vector2 Force = new Vector2(Velocity.X * Mass, 0);
                Velocity = Vector2.Zero;

                Force = RotateVector(Force, TotalUpperRotation());

                MasterProp.Velocity += Force / MasterProp.Mass;
                MasterProp.AngularVelocity += Torque / MasterProp.Inertia * 5;
            }
        }*/

        /// <summary>
        /// Moves all props associated with the current prop by a vector V.
        /// </summary>
        public void Nudge(Vector3 V)
        {
            if (MasterProp != null)
            {
                MasterProp.Nudge(V);
            }
            else
            {
                Position += V;
                //Frame.OriginOffset += V;
                Frame.ReferencePoint.Position = Position;
                //Position = Frame.ReferencePoint.Position + Frame.RotateOffset(Frame.OriginOffset, Frame.RotationOffset);
            }
        }

        public void LoadPropData(string FileName)
        {
            //StorageContainer Container = Device.OpenContainer("Props");

            // Get the path of the save game.
            //FileName = Path.Combine(Container.Path, FileName);
            string LoadName = "Props/" + FileName;

            if (File.Exists(LoadName))
            {
                List<string> Lines = new List<string>();
                StreamReader SR = new StreamReader(LoadName);

                string CurrentLine = SR.ReadLine();
                while (CurrentLine != null)
                {
                    Lines.Add(CurrentLine);
                    CurrentLine = SR.ReadLine();
                }

                for (int s = 0; s < Lines.Count; s++)
                {
                    if (Lines[s].StartsWith("Offsets = "))
                    {
                        List<Vector3> Offsets = new List<Vector3>();
                        int NumOfOffsets = int.Parse(Lines[s].Substring("Offsets = ".Length));
                        for (int o = 0; o < NumOfOffsets; o++)
                        {
                            Vector3 Offset = new Vector3();
                            int XLoc, YLoc, ZLoc;
                            XLoc = Lines[s + o + 1].IndexOf('X');
                            YLoc = Lines[s + o + 1].IndexOf('Y');
                            ZLoc = Lines[s + o + 1].IndexOf('Z');
                            Offset.X = float.Parse(Lines[s + o + 1].Substring(XLoc + 2, YLoc - XLoc - 3));
                            Offset.Y = float.Parse(Lines[s + o + 1].Substring(YLoc + 2, ZLoc - YLoc - 3));
                            Offset.Z = float.Parse(Lines[s + o + 1].Substring(ZLoc + 2, Lines[s + o + 1].Length - ZLoc - 3));
                            Offsets.Add(Offset);
                        }
                        Frame.SetPoints(Offsets);
                        s += NumOfOffsets;
                    }
                    else if (Lines[s].StartsWith("Mass = "))
                    {
                        if (Lines[s].Substring("Mass = ".Length).Equals("Infinity"))
                        {
                            Mass = float.PositiveInfinity;
                        }
                        else
                        {
                            Mass = float.Parse(Lines[s].Substring("Mass = ".Length));
                        }
                        //MassField.Text = CurrentProp.Mass.ToString();
                    }
                    else if (Lines[s].StartsWith("Inertia = "))
                    {
                        if (Lines[s].Substring("Inertia = ".Length).Equals("Infinity"))
                        {
                            Inertia = float.PositiveInfinity;
                        }
                        else
                        {
                            Inertia = float.Parse(Lines[s].Substring("Inertia = ".Length));
                        }
                        //InertiaField.Text = CurrentProp.Inertia.ToString();
                    }
                    else if (Lines[s].StartsWith("CollisionLayer = "))
                    {
                        List<int> Layers = new List<int>();
                        int NumOfLayers = int.Parse(Lines[s].Substring("CollisionLayer = ".Length));
                        for (int l = 0; l < NumOfLayers; l++)
                        {
                            int Layer;
                            Layer = int.Parse(Lines[s + l + 1]);
                            Layers.Add(Layer);
                        }
                        CollisionLayer = Layers;

                        /*CollisionField.Text = "";
                        foreach (int c in CurrentProp.CollisionLayer)
                            CollisionField.Text += c + " ";*/

                        s += NumOfLayers;
                    }
                    else if (Lines[s].StartsWith("GravityOn = "))
                    {
                        GravityOn = bool.Parse(Lines[s].Substring("GravityOn = ".Length));
                    }
                }
                Sprite = new Sprite(FileName);
                if (Sprite.Name.Equals("NullSprite.txt"))
                {
                    Sprite = null;
                }
            }
        }

        /// <summary>
        /// Returns true if two props share a collision layer.
        /// </summary>
        public static bool HasCommonLayer(Prop P1, Prop P2)
        {
            bool LayerShared = false;
            foreach (int n in P1.CollisionLayer)
            {
                foreach (int m in P2.CollisionLayer)
                {
                    if (n == m)
                        LayerShared = true;
                }
            }
            return LayerShared;
        }

        /// <summary>
        /// Returns the perpendicular of the given vector (90 degree rotation clockwise).
        /// </summary>
        public static Vector2 PerpendicularOfVector(Vector2 V)
        {
            Vector2 Perp = new Vector2(-V.Y, V.X);
            return Perp;
        }
        
        /// <summary>
        /// Returns the projection of the Light vector onto the Canvas vector as the Shadow vector.
        /// </summary>
        public static Vector3 Projection(Vector3 Canvas, Vector3 Light)
        {
            float Length = Vector3.Dot(Light, Canvas) / Canvas.Length();
            Vector3 DirectionVector = Canvas / Canvas.Length();
            Vector3 Shadow = Length * DirectionVector;
            return Shadow;
        }

        /// <summary>
        /// Rotates the given vector by the given angle and returns it.
        /// </summary>
        /*public static Vector3 RotateVector(Vector3 V, Vector3 Rotation)
        {
            
            V = RotateVectorAboutY(V, Rotation.Y);
            V = RotateVectorAboutZ(V, Rotation.Z);
            V = RotateVectorAboutX(V, Rotation.X);
            return V;
        }*/

        public static Vector3 RotateVectorAboutX(Vector3 V, float Rotation)
        {
            Vector3 RotV;
            RotV.X = V.X;
            RotV.Y = V.Y * (float)Math.Cos(Rotation) - V.Z * (float)Math.Sin(Rotation);
            RotV.Z = V.Y * (float)Math.Sin(Rotation) + V.Z * (float)Math.Cos(Rotation);
            return RotV;
        }
        public static Vector3 RotateVectorAboutY(Vector3 V, float Rotation)
        {
            Vector3 RotV;
            RotV.Y = V.Y;
            RotV.Z = V.Z * (float)Math.Cos(Rotation) - V.X * (float)Math.Sin(Rotation);
            RotV.X = V.Z * (float)Math.Sin(Rotation) + V.X * (float)Math.Cos(Rotation);
            return RotV;
        }
        public static Vector3 RotateVectorAboutZ(Vector3 V, float Rotation)
        {
            Vector3 RotV;
            RotV.Z = V.Z;
            RotV.X = V.X * (float)Math.Cos(Rotation) - V.Y * (float)Math.Sin(Rotation);
            RotV.Y = V.X * (float)Math.Sin(Rotation) + V.Y * (float)Math.Cos(Rotation);
            return RotV;
        }
    }
}